package net.yuxianghe.chromepool;

import org.apache.commons.lang3.time.DateUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeDriverService;

import java.io.File;
import java.io.IOException;
import java.util.Date;
import java.util.Set;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created by hxy on 2017/5/21.
 */
public class ChromePool implements DriverPool {

    private static Log log = LogFactory.getLog(ChromePool.class);

    private int poolSize;
    private int maxAliveTime;  //seconds
    private String chromeDriverPath;
    private ChromeDriverService chromeDriverService;
    private AtomicInteger usedChromeDriver = new AtomicInteger(0);

    private AtomicBoolean runFlag = new AtomicBoolean(false);

    private final static int listenerTime = 5000;


    @Override
    public String getHtml(String url, int waitSecondsTime) {
        ChromeDriverObject chromeDriverObject = getFreeChromeDriver();
        if (chromeDriverObject == null) {
            log.error("chromeDriverObject is null.");
            return null;
        }
        String result = new WebService()
                .setWebDriver(chromeDriverObject.getChromeDriver())
                .getHtml(url, waitSecondsTime);
        returnChromePool(chromeDriverObject);
        return result;
    }


    private Set<ChromeDriverObject> chromeDriverObjectSet = new ConcurrentSkipListSet<>();


    @Override
    public synchronized ChromePool init(int poolSize, int maxAliveTime, String chromeDriverPath) {
        this.runFlag.set(true);
        this.poolSize = poolSize;
        this.maxAliveTime = maxAliveTime;
        this.chromeDriverPath = chromeDriverPath;

        System.getProperties().setProperty("webdriver.chrome.driver", chromeDriverPath);
        this.chromeDriverService = new ChromeDriverService.Builder()
                .usingDriverExecutable(new File(chromeDriverPath))
                .usingAnyFreePort()
                .build();

        try {
            chromeDriverService.start();
        } catch (IOException e) {
            e.printStackTrace();
        }

        for (int i = 0; i < poolSize; i++) {
            putChromeDriver();
        }
        startListener();
        return this;
    }

    private synchronized void startListener() {
        Thread thread = new Thread(() -> {
            while (true) {
                for (ChromeDriverObject chromeDriverObject : chromeDriverObjectSet) {
                    if (chromeDriverObject == null) {
                        chromeDriverObjectSet.remove(null);
                        continue;
                    }
                    boolean runStatus = chromeDriverObject.isRunStatus();
                    Date now = new Date();
                    Date endDate = chromeDriverObject.getEndTime();
                    if (now.getTime() > endDate.getTime()) {
                        if (runStatus) {
                            log.warn("the " + chromeDriverObject.getUuid() + " is run...wait...");
                        }
                        ChromeDriver chromeDriver = chromeDriverObject.getChromeDriver();
                        chromeDriver.quit();
                        chromeDriverObjectSet.remove(chromeDriverObject);
                        putChromeDriver();
                    }
                }
                //监听器listenerTime跑一次
                try {
                    Thread.sleep(listenerTime);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();
    }

    private synchronized boolean putChromeDriver() {
        if (chromeDriverObjectSet.size() >= poolSize) {
            return false;
        }
        ChromeDriverObject chromeDriverObject = new ChromeDriverObject();
        ChromeDriver chromeDriver = new ChromeDriver();
        chromeDriverObject.setChromeDriver(chromeDriver);
        Date startDate = new Date();
        chromeDriverObject.setStartTime(startDate);
        Date endDate = DateUtils.addMilliseconds(startDate, maxAliveTime);
        chromeDriverObject.setEndTime(endDate);
        chromeDriverObject.setAliveTime(maxAliveTime);
        chromeDriverObject.setRunStatus(false);
        chromeDriverObjectSet.add(chromeDriverObject);
        return true;
    }


    @Override
    public synchronized ChromeDriverObject getFreeChromeDriver() {
        if (!runFlag.get()) {
            throw new RuntimeException("the chrome pool is shutdown.");
        }
        while (true) {
            if (poolSize - usedChromeDriver.get() <= 0) {
                log.info("can't found free chrome driver...wait...continue..");
                continue;
            }
            for (ChromeDriverObject chromeDriverObject : chromeDriverObjectSet) {
                if (!chromeDriverObject.isRunStatus()) {
                    log.info("find run status  is false" + chromeDriverObject.getUuid());
                    chromeDriverObject.setRunStatus(true);
                    chromeDriverObjectSet.add(chromeDriverObject);
                    usedChromeDriver.incrementAndGet();
                    return chromeDriverObject;
                }
            }
        }
    }

    @Override
    public boolean returnChromePool(ChromeDriverObject chromeDriverObject) {
        if (usedChromeDriver.get() <= 0) {
            return false;
        }
        chromeDriverObject.setRunStatus(false);
        chromeDriverObjectSet.add(chromeDriverObject);
        usedChromeDriver.decrementAndGet();
        return true;
    }


    @Override
    public synchronized boolean stop() {
        for (ChromeDriverObject chromeDriverObject : chromeDriverObjectSet) {
            chromeDriverObject.getChromeDriver().quit();
        }
        this.runFlag.set(false);
        chromeDriverObjectSet.clear();
        chromeDriverService.stop();
        return true;
    }

    @Override
    public synchronized boolean haveFreeChromeDriver() {
        return poolSize - usedChromeDriver.get() > 0;
    }


    public int getPoolSize() {
        return poolSize;
    }

    public void setPoolSize(int poolSize) {
        this.poolSize = poolSize;
    }

    public int getMaxAliveTime() {
        return maxAliveTime;
    }

    public void setMaxAliveTime(int maxAliveTime) {
        this.maxAliveTime = maxAliveTime;
    }

    public String getChromeDriverPath() {
        return chromeDriverPath;
    }

    public void setChromeDriverPath(String chromeDriverPath) {
        this.chromeDriverPath = chromeDriverPath;
    }

    public ChromeDriverService getChromeDriverService() {
        return chromeDriverService;
    }

    public void setChromeDriverService(ChromeDriverService chromeDriverService) {
        this.chromeDriverService = chromeDriverService;
    }

    public Set<ChromeDriverObject> getChromeDriverObjectSet() {
        return chromeDriverObjectSet;
    }

    public void setChromeDriverObjectSet(Set<ChromeDriverObject> chromeDriverObjectSet) {
        this.chromeDriverObjectSet = chromeDriverObjectSet;
    }
}
